package com.wctj.api.base.utils.lambda;

import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.ResultMap;
import org.apache.ibatis.annotations.ResultType;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.builder.StaticSqlSource;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.RawSqlSource;
import org.apache.ibatis.scripting.xmltags.*;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

public class QuerySupportUtils {

    public static <T> T getFieldValue(Class<T> fieldClass, String fieldName, Object target) {
        Field field = ReflectionUtils.findField(target.getClass(), fieldName);
        ReflectionUtils.makeAccessible(field);
        return (T) ReflectionUtils.getField(field, target);
    }

    public static void setFieldValue(Object target, String fieldName, Object value) {
        Field field = ReflectionUtils.findField(target.getClass(), fieldName);
        ReflectionUtils.makeAccessible(field);
        ReflectionUtils.setField(field, target, value);
    }


    public static List<org.apache.ibatis.mapping.ResultMap> getStatementResultMaps(MapperBuilderAssistant assistant, String resultMap, Class resultType, String id) {
        return (List<org.apache.ibatis.mapping.ResultMap>) invokeMethod(assistant, "getStatementResultMaps", new Class[] {String.class, Class.class, String.class}, resultMap, resultType, id);
    }

    /**
     * 执行方法
     * @param target
     * @param name
     * @param paramTypes
     * @param args
     * @return
     */
    public static Object invokeMethod(Object target, String name, Class[] paramTypes, Object ...args) {
        Method method = findMethod(target, name, paramTypes);
        return invokeMethod(target, method, args);
    }

    public static Method findMethod(Object target, String name, Class[] paramTypes) {
        Class<?> clazz = target.getClass();
        return ReflectionUtils.findMethod(clazz, name, paramTypes);
    }


    public static Object invokeMethod(Object target, Method method, Object ...args) {
        ReflectionUtils.makeAccessible(method);
        return ReflectionUtils.invokeMethod(method, target, args);
    }

    /**
     * 获取mappedStatement方法所应用的result map,返回值类型分别为class或string
     * 类型优先级 (若未存在注解,当类型与mapper泛型不一致时使用其本身) @ResultType > @ResultMap > @Results > @Table(在类型一致时应用存在的resultMap)
     * @param method
     * @param resultType
     * @param table
     * @return
     */
    public static Object getMethodResultMap(Method method, Class<?> resultType, TableInfo table, Class mapperClass) {
        String resultMap = null;
        ResultType resultTypeAnnotation = AnnotationUtils.findAnnotation(method, ResultType.class);
        if (resultTypeAnnotation != null) {
            resultType = resultTypeAnnotation.value();
        } else {
            ResultMap resultMapAnnotation = AnnotationUtils.findAnnotation(method, ResultMap.class);
            if (resultMapAnnotation != null) {
                StringBuilder sb = new StringBuilder();
                for (String value : resultMapAnnotation.value()) {
                    if (StringUtils.isEmpty(value)) {
                        continue;
                    }
                    sb.append(value);
                    sb.append(",");
                }
                int len = sb.length();
                if (len > 0) {
                    sb.delete(len - 1, len);
                }
                resultMap = sb.toString();
            }

            if (StringUtils.isEmpty(resultMap)) {
                Results results = AnnotationUtils.findAnnotation(method, Results.class);
                if (results != null) {
                    resultMap = generateResultMapName(results, method, mapperClass);
                } else {
                    /**
                     * find method return result type
                     */
                    Class currentResultType = null;
                    Type type = method.getGenericReturnType();
                    if (type != null) {
                        if (type instanceof ParameterizedType) {
                            currentResultType = (Class) ((ParameterizedType) type).getActualTypeArguments()[0];
                        } else if (type instanceof Class) {
                            currentResultType = (Class) type;
                        }
                    }

                    if (resultType.equals(currentResultType)) {
                        if (table != null) {
                            /**
                             * method return result type eq resultType apply resultMap
                             */
                            resultMap = table.getResultMap();
                        }
                    } else {
                        resultType = currentResultType == null ? resultType : currentResultType;
                    }
                }
            }

        }
        if (StringUtils.isNotEmpty(resultMap)) {
            return resultMap;
        }
        return resultType;
    }

    public static String generateResultMapName(Results results, Method method, Class mapperClass) {
        if (results != null && !results.id().isEmpty()) {
            return mapperClass.getName() + "." + results.id();
        } else {
            StringBuilder suffix = new StringBuilder();
            Class[] parameterTypes = method.getParameterTypes();
            int length = parameterTypes.length;

            for(int i = 0; i < length; ++ i) {
                Class<?> c = parameterTypes[i];
                suffix.append("-");
                suffix.append(c.getSimpleName());
            }

            if (suffix.length() < 1) {
                suffix.append("-void");
            }
            return mapperClass.getName() + "." + method.getName() + suffix;
        }
    }


    public static MetaObject getMetaObject(MappedStatement mappedStatement) {
        return SystemMetaObject.forObject(mappedStatement);
    }


    public static String getSqlText(SqlNode sqlNode) {
        if (sqlNode != null) {
            if (sqlNode instanceof TextSqlNode) {
                return getFieldValue(String.class, "text", sqlNode);
            } else if (sqlNode instanceof MixedSqlNode) {
                List<SqlNode> sqlNodeList = getFieldValue(List.class, "contents", sqlNode);
                Assert.isTrue(sqlNodeList != null && !sqlNodeList.isEmpty(), "MixedSqlNode SqlNode contents should not empty");
                StringBuilder sb = new StringBuilder();
                for (SqlNode node: sqlNodeList) {
                    sb.append(getSqlText(node));
                }
                return sb.toString();
            } else if (sqlNode instanceof StaticTextSqlNode) {
                return getFieldValue(String.class, "text", sqlNode);
            } else if (sqlNode instanceof VarDeclSqlNode) {
                return " <bind name=\""+ getFieldValue(String.class, "name", sqlNode)
                        +"\" value =\"" + getFieldValue(String.class, "expression", sqlNode)  + " \" /> ";
            }
        }
        return null;
    }

    /**
     * 通过sqlSource获取sql语句(主要为获取查询基本语句),并不支持动态条件的解析(支持xml中bind、include标签)
     * @param sqlSource
     * @return
     */
    public static String getSqlText(SqlSource sqlSource) {
        if (sqlSource != null) {
            if (sqlSource instanceof RawSqlSource) {
                return getSqlText(getFieldValue(RawSqlSource.class, "sqlSource", sqlSource));
            } else if (sqlSource instanceof StaticSqlSource) {
                return getFieldValue(String.class, "sql", sqlSource);
            } else if (sqlSource instanceof DynamicSqlSource) {
                SqlNode sqlNode = getFieldValue(SqlNode.class, "rootSqlNode", sqlSource);
                return getSqlText(sqlNode);
            }
        }
        return null;
    }


    /**
     * 获取实体类属性所对应的列名
     * @param entityClass
     * @param property
     * @return
     */
    public static String getColumn(Class entityClass, String property) {
        String[] columns = getColumns(entityClass, property);
        if (columns == null || columns.length == 0) {
            throw new IllegalArgumentException(String.format("not found %s property %s column", entityClass.getName(), property));
        }
        return columns[0];
    }


    /**
     * 获取实体类属性数组所对应的列名列表
     * @param entityClass
     * @param properties
     * @return
     */
    public static List<String> getColumnList(Class entityClass, String... properties) {
        List<String> columnList = new ArrayList<String>(properties.length);
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
        if (tableInfo != null) {
            int propertiesLength = properties.length;
            if (propertiesLength == 1 && tableInfo.getKeyProperty().equals(properties[0])) {
                columnList.add(tableInfo.getKeyColumn());
            } else {
                List<TableFieldInfo> tableInfoFieldList = tableInfo.getFieldList();
                for (String property : properties) {
                    if (tableInfo.getKeyProperty().equals(property)) {
                        columnList.add(tableInfo.getKeyColumn());
                    } else {
                        if (tableInfoFieldList != null) {
                            for (TableFieldInfo tableFieldInfo : tableInfoFieldList) {
                                if (tableFieldInfo.getProperty().equals(property)) {
                                    columnList.add(tableFieldInfo.getColumn());
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }
        return columnList;
    }

    /**
     * 获取实体类属性数组所对应的列名数组
     * @param entityClass
     * @param properties
     * @return
     */
    public static String[] getColumns(Class entityClass, String... properties) {
        List<String> columnList = getColumnList(entityClass, properties);
        return columnList.toArray(new String[columnList.size()]);
    }

}
